package chip;


/**
 * @author Mathieu Rousseau
 * @email mathieu.rousseau3@mail.mcgill.ca
 */


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;


/** ------------------------------------------------------------------------------------------------------------------
 * start of ChipAnalyzer class
 * Version 1.0
 * January 18th, 2011
 * ------------------------------------------------------------------------------------------------------------------  */
public class ChIPanalyzer {

	
	/*  global variables  */
	private static final String inputPath = "ChIPanalyzer/Input/";
	private static final String outputPath = "ChIPanalyzer/Output/";
	private static final String liftOverPath = "ChIPanalyzer/liftOver/";
	private static ArrayList<String> antibodyFilenames = new ArrayList<String>();
	private static ArrayList<String> backgroundFilenames = new ArrayList<String>();
	private static String positionsFilename = "positions_default.pos";
	private static String regionsFilename = "regions_default.xls";
	private static ArrayList<ArrayList<ArrayList<String>>> antibodyIntensities;
	private static ArrayList<ArrayList<ArrayList<String>>> backgroundIntensities;
	private static HashMap<String, Integer> regionsHashMap;
	private static HashMap<String, ArrayList<String>> positionsHashMap;
	private static String outputFilter = null;
	private static final String negativeControlFilter = "RANDOM";
	private static ArrayList<ArrayList<ArrayList<String>>> antibodySpecifiedRegions;
	private static ArrayList<ArrayList<ArrayList<String>>> backgroundSpecifiedRegions;
	private static ArrayList<ArrayList<ArrayList<String>>> antibodyNegativeControls;
	private static ArrayList<ArrayList<ArrayList<String>>> backgroundNegativeControls;
	private static int numberOfMatchedSamples = 0;
	private static ArrayList<ArrayList<ArrayList<String>>> log2Ratios;
	private static ArrayList<ArrayList<ArrayList<String>>> binnedPositiveLists;
	private static ArrayList<ArrayList<ArrayList<String>>> binnedNegativeLists;
	private static int startIndex;
	private static int endIndex;
	private static final String fileHeaderForList = "chr\tstart\tstop\tPROBE_ID\tSIGNAL\tstrand";
	private static final String fileHeaderForListNoStrand = "chr\tstart\tstop\tPROBE_ID\tSIGNAL";
	private static ArrayList<String> filesToLiftOver;
	private static ArrayList<String> filesDoneLiftOver;
	/*  end of global variables  */
	
	
	/*  start of fillArrayFromFile method  */
	private static void fillArrayFromFile(String filename, ArrayList<ArrayList<String>> arrayToFill){
		String line = null;
		int lineCounter = 0;
		try{
			BufferedReader br = new BufferedReader(new FileReader(inputPath + filename));			
			while((line = br.readLine()) != null){
				String values[] = line.split("\t");
				//  Note: there are two header lines
				//        the second header line specifies the column headers
				if(lineCounter < 2){
					//  ignore the two header lines
					//  do nothing
//					System.out.println(line);
				}
				else{
					ArrayList<String> currentLine = new ArrayList<String>();
					for(String inputValue: values){
						currentLine.add(inputValue);
					}
					//  add the start and end genomic positions for each entry
					if(positionsHashMap.containsKey(currentLine.get(3))){
						ArrayList<String> positionsHashMapEntry = positionsHashMap.get(currentLine.get(3));
						currentLine.add(positionsHashMapEntry.get(1));																						//  the chromosome
						currentLine.add(positionsHashMapEntry.get(3));																						//  the start position
						currentLine.add(String.valueOf(Integer.parseInt(positionsHashMapEntry.get(3)) + Integer.parseInt(positionsHashMapEntry.get(4))));	//  the end position
					}
					else{
//						System.out.println("Error: could not find a genomic position in the positions file for PROBE_ID: " + currentLine.get(3));
//						System.out.println("System.exit(0)");
//						System.exit(0);
					}
					currentLine.trimToSize();
					arrayToFill.add(currentLine);
//					System.out.println(line);
//					System.out.println(values.length);
				}
				lineCounter++;
			}
			arrayToFill.trimToSize();
			br.close();
		}catch (IOException e) {
			e.printStackTrace();
			System.out.println("Error: cannot find feature file with filename \"" + filename + "\"\n" +
			"System.exit(0)");
			System.exit(0);
		}
//		System.out.println("number of lines = " + lineCounter);
		System.out.println("\t\tOpened and read file: " + filename);
	}/*  end of fillArrayFromFile method  */
	
	
	/*  start of readFiles method  */
	private static void readFiles(){
		System.out.println("\n\tReading the input files...");
		
		//  make sure that there are the same number of antibody and background files specified
		antibodyFilenames.trimToSize();
		backgroundFilenames.trimToSize();
		if(antibodyFilenames.size() != backgroundFilenames.size()){
			System.out.println("Error: there are not the same number of antibody files (" + antibodyFilenames.size() +
					") as there are background files (" + backgroundFilenames.size() + ").\nSystem.exit(0)");
			System.exit(0);
		}
		else{
			//  set the number of matched experiments to be examined
			numberOfMatchedSamples = antibodyFilenames.size();
		}
		
		
		//  read the regions file and store the values
		regionsHashMap = new HashMap<String, Integer>();
		String line = null;
		int lineCounter = 0;
		try{
			BufferedReader br = new BufferedReader(new FileReader(inputPath + regionsFilename));			
			while((line = br.readLine()) != null){
				String values[] = line.split("\t");
				//  Note: there is one header line that specifies the column headers
				if(lineCounter == 0){
					//  do nothing
//					System.out.println(line);
				}
				else{
					if(values.length != 2){
						System.out.println("Error: there are not exactly two entries in each row of the regions file (there are " + values.length + " values)");
						System.out.println("\tline number " + lineCounter + ":\t" + line);
						System.out.println("System.exit(0)");
						System.exit(0);
					}					
					regionsHashMap.put(values[0], Integer.valueOf(values[1]));
//					System.out.println(line);
				}
				lineCounter++;
			}
			br.close();
		}catch (IOException e) {
			e.printStackTrace();
			System.out.println("Error: cannot find regions file with filename \"" + regionsFilename + "\"\n" +
			"System.exit(0)");
			System.exit(0);
		}
//		System.out.println("number of lines = " + lineCounter);
		System.out.println("\t\tOpened and read file: " + regionsFilename);
		
		
		//  read the positions file and store the values
		positionsHashMap = new HashMap<String, ArrayList<String>>();
		line = null;
		lineCounter = 0;
		try{
			BufferedReader br = new BufferedReader(new FileReader(inputPath + positionsFilename));			
			while((line = br.readLine()) != null){
				String values[] = line.split("\t");
				//  Note: there is one header line that specifies the column headers
				if(lineCounter == 0){
					//  ignore the header line
					//  do nothing
//					System.out.println(line);
				}
				else{
					ArrayList<String> currentLine = new ArrayList<String>();
					for(String inputValue: values){
						currentLine.add(inputValue);
					}
					currentLine.trimToSize();
					positionsHashMap.put(values[2], currentLine);
//					System.out.println(line);
				}
				lineCounter++;
			}
			br.close();
		}catch (IOException e) {
			e.printStackTrace();
			System.out.println("Error: cannot find positions file with filename \"" + positionsFilename + "\"\n" +
			"System.exit(0)");
			System.exit(0);
		}
//		System.out.println("number of lines = " + lineCounter);
		System.out.println("\t\tOpened and read file: " + positionsFilename);
		
				
		//  read all of the intensity files and store the values
		antibodyIntensities = new ArrayList<ArrayList<ArrayList<String>>>(numberOfMatchedSamples);
		backgroundIntensities = new ArrayList<ArrayList<ArrayList<String>>>(numberOfMatchedSamples);
		
		
		//  first for the antibody files
		for(String filename: antibodyFilenames){
			ArrayList<ArrayList<String>> currentFileIntensities = new ArrayList<ArrayList<String>>();
			fillArrayFromFile(filename, currentFileIntensities);
			currentFileIntensities.trimToSize();
			antibodyIntensities.add(currentFileIntensities);
		}
		antibodyIntensities.trimToSize();
		
		
		//  next for the background files
		for(String filename: backgroundFilenames){
			ArrayList<ArrayList<String>> currentFileIntensities = new ArrayList<ArrayList<String>>();
			fillArrayFromFile(filename, currentFileIntensities);
			currentFileIntensities.trimToSize();
			backgroundIntensities.add(currentFileIntensities);
		}
		backgroundIntensities.trimToSize();
		
		
		//  done with the positions hashMap so free memory
		positionsHashMap = null;
		
		System.out.println("\t\tDone");
		
		/*  							THE ORDER OF THE COLUMNS IN THE INTENSITY LISTS IS:
		 * 	
		 * 	X   Y   SEQ_ID   PROBE_ID   X_PIXEL   Y_PIXEL   HEIGHT   WIDTH   FGD_PIX   SIGNAL_MEAN   SIGNAL_STDEV   CHR   START   END
		 *  0   1      2        3          4         5        6        7        8          9              10         11     12     13
		 *    
		 */		
	}/*  end of readFiles method  */
	
	
	/*  start of getSpecifiedRegions method  */
	private static void getSpecifiedRegions(ArrayList<ArrayList<ArrayList<String>>> allIntensityList, ArrayList<ArrayList<ArrayList<String>>> specifiedRegionsIntensities){
		//  go through each of the lists of intensities
		for(int i=0; i<allIntensityList.size(); i++){
			//  create the new list which will hold only the specified entries
			ArrayList<ArrayList<String>> newSpecifiedRegionsIntensityList = new ArrayList<ArrayList<String>>(allIntensityList.get(i).size());
			//  go through all of the entries in the current intensity list
			for(int j=0; j<allIntensityList.get(i).size(); j++){
				//  check if the region for the current entry is contained in the list of specified regions
				if(regionsHashMap.containsKey(allIntensityList.get(i).get(j).get(2))){
					//  create the new list which will hold the values of the entry
					ArrayList<String> newEntrySpecifiedRegions = new ArrayList<String>(5);
					//  perform a deep copy of the current entry
					newEntrySpecifiedRegions.add(allIntensityList.get(i).get(j).get(11));		//  the chromosome
					newEntrySpecifiedRegions.add(allIntensityList.get(i).get(j).get(12));		//  the start location
					newEntrySpecifiedRegions.add(allIntensityList.get(i).get(j).get(13));		//  the end location
					newEntrySpecifiedRegions.add(allIntensityList.get(i).get(j).get(3));		//  the PROBE_ID
					newEntrySpecifiedRegions.add(allIntensityList.get(i).get(j).get(9));		//  the SIGNAL_MEAN
					if(allIntensityList.get(i).get(j).get(3).indexOf("00P") > 0){
						//  the positive strand
						newEntrySpecifiedRegions.add("+");										//  the strand
					}
					else if(allIntensityList.get(i).get(j).get(3).indexOf("99P") > 0){
						//  the nagative strand
						newEntrySpecifiedRegions.add("-");										//  the strand
					}
					else{
						System.out.println("Error: cannot determine which strand PROBE_ID " + allIntensityList.get(i).get(j).get(3) + " comes from.");
						System.out.println("System.exit(0)");
						System.exit(0);
					}
					newEntrySpecifiedRegions.trimToSize();
					newSpecifiedRegionsIntensityList.add(newEntrySpecifiedRegions);
				}
			}
			newSpecifiedRegionsIntensityList.trimToSize();
			specifiedRegionsIntensities.add(newSpecifiedRegionsIntensityList);
		}
		specifiedRegionsIntensities.trimToSize();		
	}/*  end of getSpecifiedRegions method  */
	
	
	/*  start of getNegativeControls method  */
	private static void getNegativeControls(ArrayList<ArrayList<ArrayList<String>>> allIntensityList, ArrayList<ArrayList<ArrayList<String>>> negativeControlIntensities){
		//  go through each of the lists of intensities
		for(int i=0; i<allIntensityList.size(); i++){
			//  create the new list which will hold only the negative control entries
			ArrayList<ArrayList<String>> newNegativeControlsIntensityList = new ArrayList<ArrayList<String>>(allIntensityList.get(i).size());
			//  go through all of the entries in the current intensity list
			for(int j=0; j<allIntensityList.get(i).size(); j++){
				//  check if the region for the current entry corresponds to a negative control
				String currentProbeID = allIntensityList.get(i).get(j).get(3);
				if(currentProbeID.length() >= negativeControlFilter.length()){
					if(currentProbeID.substring(0, negativeControlFilter.length()).compareTo(negativeControlFilter) == 0){
						//  create the new list which will hold the values of the entry
						ArrayList<String> newEntryNegativeControl = new ArrayList<String>(5);
						//  perform a deep copy of the current entry
						//  note: want the number of entries in each row to match with the specified regions lists so using filler entries
						newEntryNegativeControl.add("chr");											//  filler for the chromosome (does not exist)
						newEntryNegativeControl.add("start");										//  filler for the start location (does not exist)
						newEntryNegativeControl.add("end");											//  filler for the end location (does not exist)
						newEntryNegativeControl.add(allIntensityList.get(i).get(j).get(3));			//  the PROBE_ID
						newEntryNegativeControl.add(allIntensityList.get(i).get(j).get(9));			//  the SIGNAL_MEAN
						newEntryNegativeControl.trimToSize();
						newNegativeControlsIntensityList.add(newEntryNegativeControl);
					}
				}				
			}
			newNegativeControlsIntensityList.trimToSize();
			negativeControlIntensities.add(newNegativeControlsIntensityList);
		}
		negativeControlIntensities.trimToSize();
	}/*  end of getNegativeControls method  */
	
	
	/*  start of calculateMedianValues method  */
	private static void calculateMedianValues(ArrayList<ArrayList<ArrayList<String>>> inputLists, double[] medianValues){
		//  process each list one at a time
		for(int i=0; i<inputLists.size(); i++){
			//  first sort the list
			Collections.sort(inputLists.get(i), new byMeanSignal());
			
			//  check if there are an odd number of elements
			if((inputLists.get(i).size() % 2) == 1){
				int indexOfMedian = inputLists.get(i).size() / 2;				//  performing integer division will result in the proper index
				medianValues[i] = Double.parseDouble(inputLists.get(i).get(indexOfMedian).get(4));
			}
			//  check if there are an even number of elements
			else if((inputLists.get(i).size() % 2) == 0){
				//  will need to take the average of two entries as the median value
				int indexOfMedian = inputLists.get(i).size() / 2;
				medianValues[i] = (Double.parseDouble(inputLists.get(i).get(indexOfMedian-1).get(4)) + Double.parseDouble(inputLists.get(i).get(indexOfMedian).get(4))) / 2.0;
			}
		}		
	}/*  end of calculateMedianValues method  */
	
	
	/*  start of normalize method  */
	private static void normalize(boolean verbose){
		System.out.println("\n\tNormalizing the intensities...");
		
		
		//  extract all of the specified region intensities
		antibodySpecifiedRegions = new ArrayList<ArrayList<ArrayList<String>>>(numberOfMatchedSamples);
		backgroundSpecifiedRegions = new ArrayList<ArrayList<ArrayList<String>>>(numberOfMatchedSamples);
		getSpecifiedRegions(antibodyIntensities, antibodySpecifiedRegions);
		getSpecifiedRegions(backgroundIntensities, backgroundSpecifiedRegions);
		
		//  output the specified region values to file before normalization
		if(verbose){
			for(int i=0; i<numberOfMatchedSamples; i++){
				Collections.sort(antibodySpecifiedRegions.get(i), new byGenomicPosition());
				Collections.sort(backgroundSpecifiedRegions.get(i), new byGenomicPosition());
				outputListToFile(antibodySpecifiedRegions.get(i), "antibodySpecifiedRegionsBeforeNormalization_" + (i+1) + ".txt", fileHeaderForList, null);
				outputListToFile(backgroundSpecifiedRegions.get(i), "backgroundSpecifiedRegionsBeforeNormalization_" + (i+1) + ".txt", fileHeaderForList, null);
			}
		}
		
		
		//  calculate the median value for each of the channels
		double[] antibodyMedianValues = new double[numberOfMatchedSamples];
		double[] backgroundMedianValues = new double[numberOfMatchedSamples];
		calculateMedianValues(antibodySpecifiedRegions, antibodyMedianValues);
		calculateMedianValues(backgroundSpecifiedRegions, backgroundMedianValues);
		
		//  display the median intensity for each channel of each slide
//		System.out.println("\nAntibodyMedian\tBackgroundMedian");
//		for(int i=0; i<numberOfMatchedSamples; i++){
//			System.out.println(antibodyMedianValues[i] + "\t" + backgroundMedianValues[i]);
//		}
		
		
		//  calculate the average median value across all of the slides for each of the two channels
		double antibodyAverageMedian = 0;
		double backgroundAverageMedian = 0;
		for(int i=0; i<numberOfMatchedSamples; i++){
			antibodyAverageMedian += antibodyMedianValues[i];
			backgroundAverageMedian += backgroundMedianValues[i];
		}
		antibodyAverageMedian /= (double)numberOfMatchedSamples;
		backgroundAverageMedian /= (double)numberOfMatchedSamples;
		
		//  display the average median intensity for each of the two channels (red, green)  
//		System.out.println("\nBEFORE normalization:");
//		System.out.println("\tAntibodyAverageMedian\t= " + antibodyAverageMedian);
//		System.out.println("\tBackgroundAverageMedian\t= " + backgroundAverageMedian);
		
		
		//  extract all of the negative control intensities
		antibodyNegativeControls = new ArrayList<ArrayList<ArrayList<String>>>(numberOfMatchedSamples);
		backgroundNegativeControls = new ArrayList<ArrayList<ArrayList<String>>>(numberOfMatchedSamples);
		getNegativeControls(antibodyIntensities, antibodyNegativeControls);
		getNegativeControls(backgroundIntensities, backgroundNegativeControls);
		
		//  output the negative control values to file before normalization			
		if(verbose){
			for(int i=0; i<numberOfMatchedSamples; i++){
				outputListToFile(antibodyNegativeControls.get(i), "antibodyNegativeControlsBeforeNormalization_" + (i+1) + ".txt", fileHeaderForListNoStrand, null);
				outputListToFile(backgroundNegativeControls.get(i), "backgroundNegativeControlsBeforeNormalization_" + (i+1) + ".txt", fileHeaderForListNoStrand, null);
			}
		}
		
		//  done with the full lists so free up memory
		antibodyIntensities = null;
		backgroundIntensities = null;
		
		
		//  for each of the experiments normalize the intensity values (specified regions and negative controls)
		//  such that the median intensity of each channel for an individual slide
		//  is equal to the average median intensity of that channel across all slides
		double antibodyChannelCorrectionValues[] = new double[numberOfMatchedSamples];
		double backgroundChannelCorrectionValues[] = new double[numberOfMatchedSamples];
		for(int i=0; i<numberOfMatchedSamples; i++){
			antibodyChannelCorrectionValues[i] = antibodyMedianValues[i] - antibodyAverageMedian;
			backgroundChannelCorrectionValues[i] = backgroundMedianValues[i] - backgroundAverageMedian;
		}
		//  normalize the specified regions intensity values
		for(int i=0; i<numberOfMatchedSamples; i++){
			if(antibodySpecifiedRegions.get(i).size() == backgroundSpecifiedRegions.get(i).size()){
				int numberOfEntries = antibodySpecifiedRegions.get(i).size();
				for(int j=0; j<numberOfEntries; j++){
					double normalizedValue = Double.parseDouble(antibodySpecifiedRegions.get(i).get(j).get(4)) - antibodyChannelCorrectionValues[i];
					if(normalizedValue < 0){
						normalizedValue = 0;
					}
					antibodySpecifiedRegions.get(i).get(j).set(4, String.valueOf(normalizedValue));
					
					normalizedValue = Double.parseDouble(backgroundSpecifiedRegions.get(i).get(j).get(4)) - backgroundChannelCorrectionValues[i];
					if(normalizedValue < 0){
						normalizedValue = 0;
					}					
					backgroundSpecifiedRegions.get(i).get(j).set(4, String.valueOf(normalizedValue));
				}
			}
			else{
				System.out.println("Error: there are not the same number of entries in the antibody and the background specified region intensity lists.\nSystem.exit(0)");
				System.exit(0);
			}
		}
		//  output the specified region values to file after median normalization
		if(verbose){
			for(int i=0; i<numberOfMatchedSamples; i++){
				Collections.sort(antibodySpecifiedRegions.get(i), new byGenomicPosition());
				Collections.sort(backgroundSpecifiedRegions.get(i), new byGenomicPosition());
				outputListToFile(antibodySpecifiedRegions.get(i), "antibodySpecifiedRegionsAfterMedianNormalization_" + (i+1) + ".txt", fileHeaderForList, null);
				outputListToFile(backgroundSpecifiedRegions.get(i), "backgroundSpecifiedRegionsAfterMedianNormalization_" + (i+1) + ".txt", fileHeaderForList, null);
			}
		}
		
		
		//  normalize the negative control intensity values
		for(int i=0; i<numberOfMatchedSamples; i++){
			if(antibodyNegativeControls.get(i).size() == backgroundNegativeControls.get(i).size()){
				int numberOfEntries = antibodyNegativeControls.get(i).size();
				for(int j=0; j<numberOfEntries; j++){
					double normalizedValue = Double.parseDouble(antibodyNegativeControls.get(i).get(j).get(4)) - antibodyChannelCorrectionValues[i];
					if(normalizedValue < 0){
						normalizedValue = 0;
					}
					antibodyNegativeControls.get(i).get(j).set(4, String.valueOf(normalizedValue));
					
					normalizedValue = Double.parseDouble(backgroundNegativeControls.get(i).get(j).get(4)) - backgroundChannelCorrectionValues[i];
					if(normalizedValue < 0){
						normalizedValue = 0;
					}
					backgroundNegativeControls.get(i).get(j).set(4, String.valueOf(normalizedValue));				
				}
			}
			else{
				System.out.println("Error: there are not the same number of entries in the antibody and the background negative control intensity lists.\nSystem.exit(0)");
				System.exit(0);
			}
		}
		//  output the negative control values to file after normalization
		if(verbose){
			for(int i=0; i<numberOfMatchedSamples; i++){
				outputListToFile(antibodyNegativeControls.get(i), "antibodyNegativeControlsAfterMedianNormalization_" + (i+1) + ".txt", fileHeaderForListNoStrand, null);
				outputListToFile(backgroundNegativeControls.get(i), "backgroundNegativeControlsAfterMedianNormalization_" + (i+1) + ".txt", fileHeaderForListNoStrand, null);
			}
		}
		
		//  calculate the median value for each of the channels
		double[] antibodyMedianValuesAfterNormalization = new double[numberOfMatchedSamples];
		double[] backgroundMedianValuesAfterNormalization = new double[numberOfMatchedSamples];
		calculateMedianValues(antibodySpecifiedRegions, antibodyMedianValuesAfterNormalization);
		calculateMedianValues(backgroundSpecifiedRegions, backgroundMedianValuesAfterNormalization);
		
//		System.out.println("\nAntibodyMedian\tBackgroundMedian");
//		for(int i=0; i<numberOfMatchedSamples; i++){
//			System.out.println(antibodyMedianValuesAfterNormalization[i] + "\t" + backgroundMedianValuesAfterNormalization[i]);
//		}
		
		
		//  calculate the average median value across all of the slides for each of the two channels
//		antibodyAverageMedian = 0;
//		backgroundAverageMedian = 0;
//		for(int i=0; i<numberOfMatchedSamples; i++){
//			antibodyAverageMedian += antibodyMedianValues[i];
//			backgroundAverageMedian += backgroundMedianValues[i];
//		}
//		antibodyAverageMedian /= (double)numberOfMatchedSamples;
//		backgroundAverageMedian /= (double)numberOfMatchedSamples;
//		
//		
//		System.out.println("\nAFTER normalization:");
//		System.out.println("\tAntibodyAverageMedian\t= " + antibodyAverageMedian);
//		System.out.println("\tBackgroundAverageMedian\t= " + backgroundAverageMedian);
		
		
		//  calculate the median intensity of the negative control spots
		double[] antibodyNegativeControlMedianValues = new double[numberOfMatchedSamples];
		double[] backgroundNegativeControlMedianValues = new double[numberOfMatchedSamples];
		calculateMedianValues(antibodyNegativeControls, antibodyNegativeControlMedianValues);
		calculateMedianValues(backgroundNegativeControls, backgroundNegativeControlMedianValues);
		
		//  display the negative control median intensity for each channel of each slide
//		System.out.println("\nAntibodyNegativeControlMedian\tBackgroundNegativeControlMedian");
//		for(int i=0; i<numberOfMatchedSamples; i++){
//			System.out.println("\t" + antibodyNegativeControlMedianValues[i] + "\t\t\t" + backgroundNegativeControlMedianValues[i]);
//		}
		

		//  output the medians and average values to file
		String outputFilename = "normalization_values[medians_and_averages].txt";
		try{						
			BufferedWriter bw = new BufferedWriter(new FileWriter(outputPath + outputFilename));
			
			bw.write("Antibody Median Values [Before normalization]:");
			bw.newLine();
			bw.flush();
			for(int i=0; i<antibodyMedianValues.length; i++){
				bw.write("\t" + (i+1) + "\t" + antibodyMedianValues[i]);
				bw.newLine();
				bw.flush();
			}
			
			bw.write("Antibody Average Median Value = " + antibodyAverageMedian);
			bw.newLine();
			bw.flush();
			
			
			bw.newLine();
			bw.write("Background Median Values [Before normalization]:");
			bw.newLine();
			bw.flush();
			for(int i=0; i<backgroundMedianValues.length; i++){
				bw.write("\t" + (i+1) + "\t" + backgroundMedianValues[i]);
				bw.newLine();
				bw.flush();
			}
			
			bw.write("Background Average Median Value = " + backgroundAverageMedian);
			bw.newLine();
			bw.flush();
			
			
			bw.newLine();
			bw.write("Antibody Channel Correction Values:");
			bw.newLine();
			bw.flush();
			for(int i=0; i<antibodyChannelCorrectionValues.length; i++){
				bw.write("\t" + (i+1) + "\t" + antibodyChannelCorrectionValues[i]);
				bw.newLine();
				bw.flush();
			}
			
			
			bw.newLine();
			bw.write("Background Channel Correction Values:");
			bw.newLine();
			bw.flush();
			for(int i=0; i<backgroundChannelCorrectionValues.length; i++){
				bw.write("\t" + (i+1) + "\t" + backgroundChannelCorrectionValues[i]);
				bw.newLine();
				bw.flush();
			}
			
			
			bw.newLine();
			bw.write("Antibody Median Values [After normalization]:");
			bw.newLine();
			bw.flush();
			for(int i=0; i<antibodyMedianValuesAfterNormalization.length; i++){
				bw.write("\t" + (i+1) + "\t" + antibodyMedianValuesAfterNormalization[i] + "\t[should equal " + antibodyAverageMedian + "]");
				bw.newLine();
				bw.flush();
			}

			
			bw.newLine();
			bw.write("Background Median Values [After normalization]:");
			bw.newLine();
			bw.flush();
			for(int i=0; i<backgroundMedianValuesAfterNormalization.length; i++){
				bw.write("\t" + (i+1) + "\t" + backgroundMedianValuesAfterNormalization[i] + "\t[should equal " + backgroundAverageMedian + "]");
				bw.newLine();
				bw.flush();
			}
			
			
			bw.newLine();
			bw.write("Negative Control (median-normalized) Antibody Median Values:");
			bw.newLine();
			bw.flush();
			for(int i=0; i<antibodyNegativeControlMedianValues.length; i++){
				bw.write("\t" + (i+1) + "\t" + antibodyNegativeControlMedianValues[i]);
				bw.newLine();
				bw.flush();
			}
			
			bw.newLine();
			bw.write("Negative Control (median-normalized) Background Median Values:");
			bw.newLine();
			bw.flush();
			for(int i=0; i<backgroundNegativeControlMedianValues.length; i++){
				bw.write("\t" + (i+1) + "\t" + backgroundNegativeControlMedianValues[i]);
				bw.newLine();
				bw.flush();
			}
			bw.close();
		}catch (IOException e) {
			e.printStackTrace();
		}//end of try-catch block
		System.out.println("\t\tOutput file: " + outputPath + outputFilename);
		
				
		//  subtract each of the negative control median intensity values from all of the corresponding specified region intensity values
		for(int i=0; i<numberOfMatchedSamples; i++){
			if(antibodySpecifiedRegions.get(i).size() == backgroundSpecifiedRegions.get(i).size()){
				int numberOfEntries = antibodySpecifiedRegions.get(i).size();
				for(int j=0; j<numberOfEntries; j++){
					double normalizedValue = Double.parseDouble(antibodySpecifiedRegions.get(i).get(j).get(4)) - antibodyNegativeControlMedianValues[i];
					if(normalizedValue < 0){
						normalizedValue = 0;
					}
					antibodySpecifiedRegions.get(i).get(j).set(4, String.valueOf(normalizedValue));
					
					normalizedValue = Double.parseDouble(backgroundSpecifiedRegions.get(i).get(j).get(4)) - backgroundNegativeControlMedianValues[i];
					if(normalizedValue < 0){
						normalizedValue = 0;
					}					
					backgroundSpecifiedRegions.get(i).get(j).set(4, String.valueOf(normalizedValue));
				}
			}
			else{
				System.out.println("Error: there are not the same number of entries in the antibody and the background specified region intensity lists.\nSystem.exit(0)");
				System.exit(0);
			}
		}
		//  output the specified region values to file after negative control normalization
		if(verbose){
			for(int i=0; i<numberOfMatchedSamples; i++){
				Collections.sort(antibodySpecifiedRegions.get(i), new byGenomicPosition());
				Collections.sort(backgroundSpecifiedRegions.get(i), new byGenomicPosition());
				outputListToFile(antibodySpecifiedRegions.get(i), "antibodySpecifiedRegionsAfterNegativeControlNormalization_" + (i+1) + ".txt", fileHeaderForList, null);
				outputListToFile(backgroundSpecifiedRegions.get(i), "backgroundSpecifiedRegionsAfterNegativeControlNormalization_" + (i+1) + ".txt", fileHeaderForList, null);
			}
		}
		
		
		//  divide the normalized antibody intensity values by their corresponding normalized background intensity values and calculate the log2 value
		log2Ratios = new ArrayList<ArrayList<ArrayList<String>>>(numberOfMatchedSamples);
		
		double logBaseChangeConstant = Math.log(2.0);			//  base 10
		
		for(int i=0; i<numberOfMatchedSamples; i++){
			
			//  start by sorting the lists so that corresponding entries line up across the two lists
			Collections.sort(antibodySpecifiedRegions.get(i), new byGenomicPosition());
			Collections.sort(backgroundSpecifiedRegions.get(i), new byGenomicPosition());
			
			if(antibodySpecifiedRegions.get(i).size() == backgroundSpecifiedRegions.get(i).size()){
				int numberOfEntries = antibodySpecifiedRegions.get(i).size();
				
				ArrayList<ArrayList<String>> newList = new ArrayList<ArrayList<String>>(numberOfEntries);
				
				for(int j=0; j<numberOfEntries; j++){
					//  make sure that the PROBE_ID are the same and that the values are corresponding entries in the two lists
					if(antibodySpecifiedRegions.get(i).get(j).get(3).compareTo(backgroundSpecifiedRegions.get(i).get(j).get(3)) == 0){
						
						double numerator = Double.parseDouble(antibodySpecifiedRegions.get(i).get(j).get(4));
						double denominator = Double.parseDouble(backgroundSpecifiedRegions.get(i).get(j).get(4));
						double ratio = 0;
						
						//  if both the numerator and denominator are NOT zero, then calculate the ratio
						if((numerator > 0) && (denominator > 0)){
							ratio = numerator / denominator;
						}
						
						ArrayList<String> newEntry = new ArrayList<String>(antibodySpecifiedRegions.get(i).get(j).size());
						for(int k=0; k<antibodySpecifiedRegions.get(i).get(j).size(); k++){
							if(k == 4){
								if(ratio < 1){
									//  if the ratio is less than one (1), then the log will be negative so instead set the log to zero (0)
									newEntry.add(String.valueOf(0));
								}
								else if(ratio >= 1){
									newEntry.add(String.valueOf(Math.log(ratio) / (double)(logBaseChangeConstant)));			//  base 10
								}
							}
							else{
								newEntry.add(antibodySpecifiedRegions.get(i).get(j).get(k));
							}
						}
						newEntry.trimToSize();
//						System.out.println(newEntry);
						newList.add(newEntry);
					}
					else{
						System.out.println("Error: the PROBE_ID do not match between the antibody and the background intensity lists. These entries do NOT correspond. Cannot take the ratio.");
						System.out.println("System.exit(0)");
						System.exit(0);
					}
				}
				newList.trimToSize();
				log2Ratios.add(newList);
			}
			else{
				System.out.println("Error: there are not the same number of entries in the antibody and the background specified region intensity lists.\nSystem.exit(0)");
				System.exit(0);
			}
			log2Ratios.trimToSize();
		}
		//  sort the final log2 ratios
		for(int i=0; i<numberOfMatchedSamples; i++){
			Collections.sort(log2Ratios.get(i), new byGenomicPosition());			
		}
		
		System.out.println("\t\tDone");
	}/*  end of normalize method  */
	
	
	/*  start of binProbes method  */
	private static void binProbes(){
		//  this method will bin the probes corresponding to the output filter
		
		
		System.out.println("\n\tBinning the probes into 50bp bins for the genomic region that corresponds to the output filter (" + outputFilter + ")...");
		
		
		//  find the chromosome corresponding to the output filter and assume that it is the same across all of the experiments
		String chromosome = "chr";
		for(ArrayList<String> currentLine: log2Ratios.get(0)){
			if(currentLine.get(3).startsWith(outputFilter)){
				chromosome = currentLine.get(0);
				break;
			}
		}
		
		
		//  create lists containing only the filtered probes for the positive and negative strands separately
		//  the HashMaps have the start index as the key and the full line of the BED file as the value
		ArrayList<HashMap<Integer, ArrayList<String>>> valuesPositiveStrand = new ArrayList<HashMap<Integer, ArrayList<String>>>(numberOfMatchedSamples);
		ArrayList<HashMap<Integer, ArrayList<String>>> valuesNegativeStrand = new ArrayList<HashMap<Integer, ArrayList<String>>>(numberOfMatchedSamples);
				
		//  keep track of the start and end genomic locations of the regions corresponding to the output filter
		int[][] genomicExtremities = new int[numberOfMatchedSamples][2];
		
		for(int i=0; i<numberOfMatchedSamples; i++){
			HashMap<Integer, ArrayList<String>> currentMapPositive = new HashMap<Integer, ArrayList<String>>();
			HashMap<Integer, ArrayList<String>> currentMapNegative = new HashMap<Integer, ArrayList<String>>();
			
			genomicExtremities[i][0] = Integer.MAX_VALUE;
			genomicExtremities[i][1] = Integer.MIN_VALUE;
			
			Collections.sort(log2Ratios.get(i), new byGenomicPosition());
			
			for(ArrayList<String> currentLine: log2Ratios.get(i)){
				if(currentLine.get(3).startsWith(outputFilter)){
					int startIndex = Integer.parseInt(currentLine.get(1));
					if(startIndex < genomicExtremities[i][0]){
						genomicExtremities[i][0] = startIndex; 
					}
					int endIndex = Integer.parseInt(currentLine.get(2));
					if(endIndex > genomicExtremities[i][1]){
						genomicExtremities[i][1] = endIndex; 
					}
					
					if(currentLine.get(5).compareTo("+") == 0){
						ArrayList<String> newEntry = new ArrayList<String>(4);
						newEntry.add(currentLine.get(0));
						newEntry.add(currentLine.get(1));
						newEntry.add(currentLine.get(2));
						newEntry.add(currentLine.get(4));
						newEntry.trimToSize();
						currentMapPositive.put(new Integer(newEntry.get(1)), newEntry);
					}
					else if(currentLine.get(5).compareTo("-") == 0){
						ArrayList<String> newEntry = new ArrayList<String>(4);
						newEntry.add(currentLine.get(0));
						newEntry.add(currentLine.get(1));
						newEntry.add(currentLine.get(2));
						newEntry.add(currentLine.get(4));
						newEntry.trimToSize();
						currentMapNegative.put(new Integer(newEntry.get(1)), newEntry);
					}					
				}
			}
			valuesPositiveStrand.add(currentMapPositive);
			valuesNegativeStrand.add(currentMapNegative);
		}
		valuesPositiveStrand.trimToSize();
		valuesNegativeStrand.trimToSize();
		
		
		//  check the genomic extremity indices
		for(int i=0; i<numberOfMatchedSamples; i++){
			if(genomicExtremities[0][0] != genomicExtremities[i][0]){
				System.out.println("Error: the start index is not consistent across experiments.\nSystem.exit(0)");
			}
			if(genomicExtremities[0][1] != genomicExtremities[i][1]){
				System.out.println("Error: the end index is not consistent across experiments.\nSystem.exit(0)");
			}
		}
		startIndex = genomicExtremities[0][0];
		endIndex = genomicExtremities[0][1];
		genomicExtremities = null;
//		System.out.println("The start index is: " + startIndex + " and the end index is: " + endIndex);
		
		
		//  bin the filtered probes into 50bp sized bins (average signal, ignore signals of zero (0))
		binnedPositiveLists = new ArrayList<ArrayList<ArrayList<String>>>();
		binnedNegativeLists = new ArrayList<ArrayList<ArrayList<String>>>();
		
		for(int i=0; i<numberOfMatchedSamples; i++){
			ArrayList<ArrayList<String>> binnedProbesPositive = new ArrayList<ArrayList<String>>();
			ArrayList<ArrayList<String>> binnedProbesNegative = new ArrayList<ArrayList<String>>();
			
			//  using a bin size of 50bp by shifting the bin location 25bp to the right of where the binning starts
			int indexOfBin = startIndex + 25;
			int indexToCountFrom = startIndex;
			while(indexOfBin < endIndex){
				double binnedSignalPositive = 0;
				double binnedSignalNegative = 0;
				
				int numberOfSignalsPositiveInBin = 0;
				int numberOfSignalsNegativeInBin = 0;
				
				for(int j=0; j<50; j++){
					if(valuesPositiveStrand.get(i).containsKey(indexToCountFrom + j)){
						binnedSignalPositive += Double.valueOf(valuesPositiveStrand.get(i).get(indexToCountFrom + j).get(3));
						numberOfSignalsPositiveInBin++;
					}
					if(valuesNegativeStrand.get(i).containsKey(indexToCountFrom + j)){
						binnedSignalNegative += Double.valueOf(valuesNegativeStrand.get(i).get(indexToCountFrom + j).get(3));
						numberOfSignalsNegativeInBin++;
					}
				}
				if(numberOfSignalsPositiveInBin > 1){
					binnedSignalPositive /= numberOfSignalsPositiveInBin;
				}
				if(numberOfSignalsNegativeInBin > 1){
					binnedSignalNegative /= numberOfSignalsNegativeInBin;
				}
				
				ArrayList<String> newBinnedEntryPositive = new ArrayList<String>(4);
				newBinnedEntryPositive.add(chromosome);										//  chromosome
				newBinnedEntryPositive.add(String.valueOf(indexOfBin));						//  start index
				newBinnedEntryPositive.add(String.valueOf(indexOfBin+50));					//  end index
				newBinnedEntryPositive.add(String.valueOf(binnedSignalPositive));			//  binned value (positive strand)
				newBinnedEntryPositive.trimToSize();
				
				ArrayList<String> newBinnedEntryNegative = new ArrayList<String>(4);
				newBinnedEntryNegative.add(chromosome);										//  chromosome
				newBinnedEntryNegative.add(String.valueOf(indexOfBin));						//  start index
				newBinnedEntryNegative.add(String.valueOf(indexOfBin+50));					//  end index
				newBinnedEntryNegative.add(String.valueOf(binnedSignalNegative));			//  binned value (negative strand)
				newBinnedEntryNegative.trimToSize();
				
				binnedProbesPositive.add(newBinnedEntryPositive);
				binnedProbesNegative.add(newBinnedEntryNegative);
				
				indexOfBin += 50;
				indexToCountFrom += 50;
			}
			binnedProbesPositive.trimToSize();
			binnedProbesNegative.trimToSize();
			
			binnedPositiveLists.add(binnedProbesPositive);
			binnedNegativeLists.add(binnedProbesNegative);			
		}
		
		
		//  free memory
		valuesPositiveStrand = null;
		valuesNegativeStrand = null;
		
		
		System.out.println("\t\tDone");
	}/*  end of binProbes method  */
	
	
	/*  start of liftOver method  */
	private static void liftOver(){
		System.out.println("\n\tliftOver from hg17 to hg18...");
		
		filesToLiftOver = new ArrayList<String>();
		outputBinnedBEDFiles();
		
		filesDoneLiftOver = new ArrayList<String>();
		for(String filename: filesToLiftOver){
			try{
				String newFilename = filename.replace("hg17", "hg18");
				filesDoneLiftOver.add(newFilename);
				
				String command = liftOverPath + "liftOver " + liftOverPath + filename + " " + liftOverPath + "hg17ToHg18.over.chain " + liftOverPath + newFilename + " " + liftOverPath + "unMapped";
//				System.out.println("\t\tCommand to execute: " + command);
				Process proc = Runtime.getRuntime().exec(command);
			
				BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
				 
				String str = null;
			    while((str = br.readLine()) != null){
//			    	System.out.println(str);
			    }
			    
			    try{
			    	int exitValue = proc.waitFor();
//			    	System.out.println("\t\tExited with error code: " + exitValue);
			    }catch (InterruptedException e){
			    	System.out.println("Error: Process was interrupted.\nSystem.exit(0)");
			    	e.printStackTrace();
			    	System.exit(0);
			    }

			    br.close();
			}catch(IOException e){
				System.out.println("Error: executing liftOver caused an IOException.\nSystem.exit(0)");
				e.printStackTrace();
				System.exit(0);
			}
		}
		
		
		//  delete all of the hg17 BED files used by the liftOver program
		for(String filename: filesToLiftOver){
			File fileToDelete = new File(liftOverPath + filename);
			fileToDelete.delete();			
		}
		filesToLiftOver = null;
		//  delete the unmapped file used by the liftOver program
		File fileToDelete = new File(liftOverPath + "unMapped");
		fileToDelete.delete();
		fileToDelete = null;
		
		
		//  define the colors for the output tracks
		ArrayList<double[]> RGBcolorCodes = new ArrayList<double[]>(numberOfMatchedSamples*2);
		
		//  first the purple
		double[] currentColor = new double[3];
		currentColor[0] = 148;
		currentColor[1] = 0;
		currentColor[2] = 211;
		RGBcolorCodes.add(currentColor);		//  for the positive strand
		RGBcolorCodes.add(currentColor);		//  for the negative strand
		
		//  second the blue
		currentColor = new double[3];
		currentColor[0] = 0;
		currentColor[1] = 0;
		currentColor[2] = 255;
		RGBcolorCodes.add(currentColor);		//  for the positive strand
		RGBcolorCodes.add(currentColor);		//  for the negative strand

		//  third the green
		currentColor = new double[3];
		currentColor[0] = 46;
		currentColor[1] = 139;
		currentColor[2] = 87;
		RGBcolorCodes.add(currentColor);		//  for the positive strand
		RGBcolorCodes.add(currentColor);		//  for the negative strand
		
		//  fourth the red
		currentColor = new double[3];
		currentColor[0] = 255;
		currentColor[1] = 99;
		currentColor[2] = 71;
		RGBcolorCodes.add(currentColor);		//  for the positive strand
		RGBcolorCodes.add(currentColor);		//  for the negative strand
		
		//  if there are more than four (4) matched samples, make all the rest of the tracks black
		for(int i=4; i<numberOfMatchedSamples; i++){
			currentColor = new double[3];
			currentColor[0] = 0;
			currentColor[1] = 0;
			currentColor[2] = 0;
			RGBcolorCodes.add(currentColor);		//  for the positive strand
			RGBcolorCodes.add(currentColor);		//  for the negative strand
		}
		
		
		RGBcolorCodes.trimToSize();
		
		
		//  add the header to the BED files that have been liftedOver to turn them into BEDGraph files
		filesDoneLiftOver.trimToSize();
		
		ArrayList<String> BEDGraphHeaderPrefix = new ArrayList<String>(numberOfMatchedSamples);
		for(int i=0; i<numberOfMatchedSamples; i++){
			String antibodyFilenamePrefix = antibodyFilenames.get(i).substring(0, antibodyFilenames.get(i).lastIndexOf('.'));
			String backgroundFilenamePrefix = backgroundFilenames.get(i).substring(0, backgroundFilenames.get(i).lastIndexOf('.'));
			String prefix = antibodyFilenamePrefix + " " + backgroundFilenamePrefix;
			BEDGraphHeaderPrefix.add(prefix);
			BEDGraphHeaderPrefix.add(prefix);			//  put this twice in the list, once for the positive strand and once for the negative strand
		}
		
		for(int i=0; i<filesDoneLiftOver.size(); i++){
			String chromosome = "chr";
			int startIndexFile = Integer.MAX_VALUE;
			int endIndexFile = Integer.MIN_VALUE;

			ArrayList<String> currentFile = new ArrayList<String>();
			//  read in the current file (it is missing the header lines)
			try{
				BufferedReader br = new BufferedReader(new FileReader(liftOverPath + filesDoneLiftOver.get(i)));			
				String line = null;
				while((line = br.readLine()) != null){
					currentFile.add(line);
					
					String[] values = line.split("\t");
					chromosome = values[0];
					int startIndexCurrentLine = Integer.parseInt(values[1]);
					int endIndexCurrentLine = Integer.parseInt(values[2]);
					if(startIndexCurrentLine < startIndexFile){
						startIndexFile = startIndexCurrentLine;
					}
					if(endIndexCurrentLine > endIndexFile){
						endIndexFile = endIndexCurrentLine;
					}
				}
				br.close();
			}catch (IOException e) {
				e.printStackTrace();
				System.exit(0);
			}
			
			
			//  output the current file in BEDGraph format (add the header lines to the current file
			String newFilename = filesDoneLiftOver.get(i).replaceFirst("BED", "BEDGraph");
			try{						
				BufferedWriter bw = new BufferedWriter(new FileWriter(liftOverPath + newFilename));
				
				
				//  write the header lines
				bw.write("browser position " + chromosome + ":" + startIndexFile + "-" + endIndexFile);
				bw.newLine();
				bw.flush();
				
				if((i%2) == 0){			//  it is a positive (+) track
					bw.write("track type=bedGraph name=\"" + BEDGraphHeaderPrefix.get(i) + " +\" description=\"ChIP-chip " + outputFilter + " (50bp bins)\" visibility=full color=" + RGBcolorCodes.get(i)[0] + "," + RGBcolorCodes.get(i)[1] + "," + RGBcolorCodes.get(i)[2]);
				}
				else if((i%2) == 1){	//  it is a negative (-) track
					bw.write("track type=bedGraph name=\"" + BEDGraphHeaderPrefix.get(i) + " -\" description=\"ChIP-chip " + outputFilter + " (50bp bins)\" visibility=full color=" + RGBcolorCodes.get(i)[0] + "," + RGBcolorCodes.get(i)[1] + "," + RGBcolorCodes.get(i)[2]);
				}
				bw.newLine();
				bw.flush();
				
				for(String lineToOutput: currentFile){
					bw.write(lineToOutput);
					bw.newLine();
					bw.flush();					
				}				
				bw.close();
			}catch (IOException e) {
				e.printStackTrace();
			}//end of try-catch block
			System.out.println("\t\tOutput file: " + newFilename);
		}
		//  delete all of the hg18 BED files converted to BEDGraph files 
		for(String filename: filesDoneLiftOver){
			fileToDelete = new File(liftOverPath + filename);
			fileToDelete.delete();
		}
		filesDoneLiftOver = null;
		
		
		System.out.println("\t\tDone");
	}/*  end of liftOver method  */
	
	
	/*  start of outputListToFile method  */
	private static void outputListToFile(ArrayList<ArrayList<String>> listToOutput, String outputFilename, String header, String filter){
		try{						
			BufferedWriter bw = new BufferedWriter(new FileWriter(outputPath + outputFilename));

			//  write the header line
			if(header != null){
				bw.write(header);
				bw.newLine();
				bw.flush();
			}

			for(ArrayList<String> lineToOutput: listToOutput){
				//  if using a filter check whether or not to output the current line
				boolean outputLine = false;
				if(filter == null){
					outputLine = true;
				}
				//  check if the current line corresponds to an entry that matches the filter
				else if(lineToOutput.get(3).startsWith(filter)){
					outputLine = true;
				}
				else{
					//  do not output the line
				}				
				if(outputLine){
					for(int j=0; j<lineToOutput.size(); j++){
						if(j<lineToOutput.size()-1){
							bw.write(lineToOutput.get(j) + "\t");
						}
						else{
							bw.write(lineToOutput.get(j));	
						}
					}
					bw.newLine();
					bw.flush();
				}				
			}				
			bw.close();
		}catch (IOException e) {
			e.printStackTrace();
		}//end of try-catch block
		System.out.println("\t\tOutput file: " + outputFilename);
	}/*  end of outputListToFile method  */
	
	
	/*  start of outputBEDfiles method  */
	private static void outputBEDfiles(){
		System.out.println("\n\tCreating the BED files...");
		
		for(int i=0; i<numberOfMatchedSamples; i++){
			Collections.sort(log2Ratios.get(i), new byGenomicPosition());
			String antibodyFilenamePrefix = antibodyFilenames.get(i).substring(0, antibodyFilenames.get(i).lastIndexOf('.'));
			String backgroundFilenamePrefix = backgroundFilenames.get(i).substring(0, backgroundFilenames.get(i).lastIndexOf('.'));
			String filename = "log2Ratios_" + antibodyFilenamePrefix + "_" + backgroundFilenamePrefix + "_" + outputFilter + ".bed";
			outputListToFile(log2Ratios.get(i), filename, "track name=" + antibodyFilenamePrefix + "_" + backgroundFilenamePrefix, outputFilter);
		}
		
		System.out.println("\t\tDone");
	}/*  end of outputBEDfiles method  */
	

	/*  start of outputBinnedBEDFiles method  */
	private static void outputBinnedBEDFiles(){
		System.out.println("\n\tCreating the BED (binned) files...");
		
		
		//  output the binned probe values to file
		for(int i=0; i<numberOfMatchedSamples; i++){
			//  first for the positive strand
			String antibodyFilenamePrefix = antibodyFilenames.get(i).substring(0, antibodyFilenames.get(i).lastIndexOf('.'));
			String backgroundFilenamePrefix = backgroundFilenames.get(i).substring(0, backgroundFilenames.get(i).lastIndexOf('.'));
			String outputFilename = "log2Ratios_BED_binned_" + antibodyFilenamePrefix + "_" + backgroundFilenamePrefix + "_" + outputFilter + "_hg17_+.bed";
			filesToLiftOver.add(outputFilename);
			
			try{						
				BufferedWriter bw = new BufferedWriter(new FileWriter(liftOverPath + outputFilename));
				
				for(ArrayList<String> lineToOutput: binnedPositiveLists.get(i)){
					bw.write(lineToOutput.get(0) + "\t" + lineToOutput.get(1) + "\t" + lineToOutput.get(2) + "\t" + lineToOutput.get(3));
					bw.newLine();
					bw.flush();					
				}				
				bw.close();
			}catch (IOException e) {
				e.printStackTrace();
			}//end of try-catch block
			System.out.println("\t\tOutput file: " + outputFilename);
			
			
			//  next for the negative strand
			outputFilename = "log2Ratios_BED_binned_" + antibodyFilenamePrefix + "_" + backgroundFilenamePrefix + "_" + outputFilter + "_hg17_-.bed";
			filesToLiftOver.add(outputFilename);
			
			try{						
				BufferedWriter bw = new BufferedWriter(new FileWriter(liftOverPath + outputFilename));
				
				for(ArrayList<String> lineToOutput: binnedNegativeLists.get(i)){
					bw.write(lineToOutput.get(0) + "\t" + lineToOutput.get(1) + "\t" + lineToOutput.get(2) + "\t" + lineToOutput.get(3));
					bw.newLine();
					bw.flush();
				}				
				bw.close();
			}catch (IOException e) {
				e.printStackTrace();
			}//end of try-catch block
			System.out.println("\t\tOutput file: " + outputFilename);
		}
		System.out.println("\t\tDone");
	}/*  end of outputBinnedBEDFiles method  */
	
	
	/*  start of outputBinnedBEDGraphFiles method  */
	private static void outputBinnedBEDGraphFiles(){
		System.out.println("\n\tCreating the BEDGraph files...");
		
		
		//  define the colors for the output tracks
		ArrayList<double[]> RGBcolorCodes = new ArrayList<double[]>(2);
		double[] currentColor = new double[3];
		//  first the purple
		currentColor[0] = 148;
		currentColor[1] = 0;
		currentColor[2] = 211;
		RGBcolorCodes.add(currentColor);
		currentColor = new double[3];
		//  second the blue
		currentColor[0] = 0;
		currentColor[1] = 0;
		currentColor[2] = 255;
		RGBcolorCodes.add(currentColor);
		
		
		//  output the binned probe values to file
		for(int i=0; i<numberOfMatchedSamples; i++){
			//  first for the positive strand
			String antibodyFilenamePrefix = antibodyFilenames.get(i).substring(0, antibodyFilenames.get(i).lastIndexOf('.'));
			String backgroundFilenamePrefix = backgroundFilenames.get(i).substring(0, backgroundFilenames.get(i).lastIndexOf('.'));
			String outputFilename = "log2Ratios_BEDGraph_" + antibodyFilenamePrefix + "_" + backgroundFilenamePrefix + "_" + outputFilter + "_+.bed";
			
			try{						
				BufferedWriter bw = new BufferedWriter(new FileWriter(outputPath + outputFilename));
				
				
				//  write the header lines
				bw.write("browser position " + binnedPositiveLists.get(i).get(0).get(0) + ":" + startIndex + "-" + endIndex);
				bw.newLine();
				bw.flush();
				
				bw.write("track type=bedGraph name=\"" + antibodyFilenamePrefix + " " + backgroundFilenamePrefix + " +\" description=\"ChIP-chip " + outputFilter + " (50bp bins)\" visibility=full color=" + RGBcolorCodes.get(i%2)[0] + "," + RGBcolorCodes.get(i%2)[1] + "," + RGBcolorCodes.get(i%2)[2]);
				bw.newLine();
				bw.flush();
				
				
				for(ArrayList<String> lineToOutput: binnedPositiveLists.get(i)){
					bw.write(lineToOutput.get(0) + "\t" + lineToOutput.get(1) + "\t" + lineToOutput.get(2) + "\t" + lineToOutput.get(3));
					bw.newLine();
					bw.flush();					
				}				
				bw.close();
			}catch (IOException e) {
				e.printStackTrace();
			}//end of try-catch block
			System.out.println("\t\tOutput file: " + outputFilename);
			
			
			//  next for the negative strand
			outputFilename = "log2Ratios_BEDGraph_" + antibodyFilenamePrefix + "_" + backgroundFilenamePrefix + "_" + outputFilter + "_-.bed";
			
			try{						
				BufferedWriter bw = new BufferedWriter(new FileWriter(outputPath + outputFilename));
				
				
				//  write the header lines
				bw.write("browser position " + binnedNegativeLists.get(i).get(0).get(0) + ":" + startIndex + "-" + endIndex);
				bw.newLine();
				bw.flush();
				
				bw.write("track type=bedGraph name=\"" + antibodyFilenamePrefix + " " + backgroundFilenamePrefix + " -\" description=\"ChIP-chip " + outputFilter + " (50bp bins)\" visibility=full color=" + RGBcolorCodes.get(i%2)[0] + "," + RGBcolorCodes.get(i%2)[1] + "," + RGBcolorCodes.get(i%2)[2]);
				bw.newLine();
				bw.flush();
			
				for(ArrayList<String> lineToOutput: binnedNegativeLists.get(i)){
					bw.write(lineToOutput.get(0) + "\t" + lineToOutput.get(1) + "\t" + lineToOutput.get(2) + "\t" + lineToOutput.get(3));
					bw.newLine();
					bw.flush();
				}				
				bw.close();
			}catch (IOException e) {
				e.printStackTrace();
			}//end of try-catch block
			System.out.println("\t\tOutput file: " + outputFilename);
		}
		System.out.println("\t\tDone");
	}/*  end of outputBinnedBEDGraphFiles method  */
	
	
	/*  start of main method  */
	public static void main(String[] args) {
		System.out.println("Starting the ChipAnalyzer Program");
		
		
		/**************************************************************************
		 *                                                                        *
		 *                                                                        *
		 *                                                                        *
		 *                        SET THE PARAMETERS BELOW                        *
		 *                                                                        *
		 *                                                                        *
		 *                                                                        *
		 **************************************************************************/
		
		//  set the filenames for the antibody feature files and the background feature files
		//  note: the order of the filenames in the two lists is used to indicate which arrays constitute matched pairs
		antibodyFilenames.add("H3Red.ftr");
		backgroundFilenames.add("H3Green.ftr");
		
		antibodyFilenames.add("K27Red.ftr");
		backgroundFilenames.add("K27Green.ftr");
		
		
		//	THP1  --  0Hr
//		antibodyFilenames.add("THP1_0h_CTCFab_Red.ftr");
//		backgroundFilenames.add("THP1_0h_CTCFab_Green.ftr");
//		
//		antibodyFilenames.add("THP1_0h_CTCFm_Red.ftr");
//		backgroundFilenames.add("THP1_0h_CTCFm_Green.ftr");
//		
//		antibodyFilenames.add("THP1_0h_H3_Red.ftr");
//		backgroundFilenames.add("THP1_0h_H3_Green.ftr");
//		
//		antibodyFilenames.add("THP1_0h_K27_Red.ftr");
//		backgroundFilenames.add("THP1_0h_K27_Green.ftr");
		
		
		//		NT2  --  CTCF
//		antibodyFilenames.add("NT2siCTCF_CTCFab_Red.ftr");
//		backgroundFilenames.add("NT2siCTCF_CTCFab_Green.ftr");
//		
//		antibodyFilenames.add("NT2siCTCF_CTCFm_Red.ftr");
//		backgroundFilenames.add("NT2siCTCF_CTCFm_Green.ftr");
//		
//		antibodyFilenames.add("NT2siNC_CTCFab_Red.ftr");
//		backgroundFilenames.add("NT2siNC_CTCFab_Green.ftr");
//		
//		antibodyFilenames.add("NT2siNC_CTCFm_Red.ftr");
//		backgroundFilenames.add("NT2siNC_CTCFm_Green.ftr");
		
		
		//		NT2  --  K27	
//		antibodyFilenames.add("NT2siCTCF_H3_Red.ftr");
//		backgroundFilenames.add("NT2siCTCF_H3_Green.ftr");
//		
//		antibodyFilenames.add("NT2siCTCF_K27_Red.ftr");
//		backgroundFilenames.add("NT2siCTCF_K27_Green.ftr");
//		
//		antibodyFilenames.add("NT2siNC_H3_Red.ftr");
//		backgroundFilenames.add("NT2siNC_H3_Green.ftr");
//		
//		antibodyFilenames.add("NT2siNC_K27_Red.ftr");
//		backgroundFilenames.add("NT2siNC_K27_Green.ftr");
		
		
		
		
		//  set the filename for the positions file
		positionsFilename = "positions.pos";
		
		
		//  set the filename for the regions file
		regionsFilename = "ChIP_regions.txt";
		

		//  set the filter for the probes to output in the BED file(s) of log2 ratios
		outputFilter = "HOXA";
		
		/**************************************************************************
		 *                                                                        *
		 *                                                                        *                                                                   
		 *                                                                        *
		 *                        SET THE PARAMETERS ABOVE                        *
		 *                                                                        *
		 *                                                                        *
		 *                                                                        *
		 **************************************************************************/
		
		
		
		
		
		
		
		
		
		
		//  read all of the files (intensities and positions) and load the data
		readFiles();
		
		
		//  normalize the intensities 
		normalize(false);												//  specify whether (true) or not (false) to use the verbose mode
		
		
		//  bin the probes into 50bp bins for the genomic regions corresponding to the output filter
		binProbes();
		
		
		//  liftOver the binned values from hg17 to hg18
		liftOver();
		
		
		//  output the BED files
//		outputBEDfiles();
		
		
		//  output the BEDGraph files		(these are the original hg17 probes)
//		outputBinnedBEDGraphFiles();
		
		
		//  Done!
		System.out.println("\nDone!");
	}/*  end of main method  */
	
	
	
	/*  start of byGenomicPosition class  */
	private static class byGenomicPosition implements java.util.Comparator{
		/*  start of compare method  */
		public int compare(Object firstEntry, Object secondEntry){
			//  Note: if the first is before the second, then the return value should be negative
			//        if the first is after the second, then the return value should be positive
			//        if the two values are equal, then the return value should be zero
			int comparisonValue = 0;
			
			
			//  cast the values to be compared
			ArrayList<String> firstList = (ArrayList<String>)firstEntry;
			ArrayList<String> secondList = (ArrayList<String>)secondEntry;
			
			int firstGenomicStartValue = Integer.parseInt(firstList.get(2));
			int secondGenomicStartValue = Integer.parseInt(secondList.get(2));
			
			
			//  compare the genome start positions
			if(firstGenomicStartValue < secondGenomicStartValue){
				comparisonValue = -1;
			}
			else if(firstGenomicStartValue > secondGenomicStartValue){
				comparisonValue = +1;
			}
			else if(firstGenomicStartValue == secondGenomicStartValue){
				//  compare the strand
				if((firstList.get(5).compareTo("+") == 0) && (secondList.get(5).compareTo("-") == 0)){
					comparisonValue = -1;
				}
				else if((firstList.get(5).compareTo("-") == 0) && (secondList.get(5).compareTo("+") == 0)){
					comparisonValue = +1;
				}
				else{
					System.out.println("Error: cannot compare the two entries.\nSystem.exit(0)");
					System.exit(0);
				}
			}
			else{
				System.out.println("Error: cannot compare the two entries.\nSystem.exit(0)");
				System.exit(0);
			}
			return comparisonValue;
		}/*  end of compare method  */
	}/*  end of byGenomicPosition class  */
	
	
	/*  start of byMeanSignal class  */
	private static class byMeanSignal implements java.util.Comparator{
		/*  start of compare method  */
		public int compare(Object firstEntry, Object secondEntry){
			//  Note: if the first is before the second, then the return value should be negative
			//        if the first is after the second, then the return value should be positive
			//        if the two values are equal, then the return value should be zero
			int comparisonValue = 0;
			
			
			//  cast and isolate the mean signal values to be compared
			double firstSignalValue = Double.valueOf(((ArrayList<String>)firstEntry).get(4));
			double secondSignalValue = Double.valueOf(((ArrayList<String>)secondEntry).get(4));
			
			
			//  compare the signal values
			if(firstSignalValue < secondSignalValue){
				comparisonValue = -1;
			}
			else if(firstSignalValue > secondSignalValue){
				comparisonValue = +1;
			}
			else if(firstSignalValue == secondSignalValue){
				//  the values are the same, do nothing			
			}
			else{
				System.out.println("Error: cannot compare the two entries.\nSystem.exit(0)");
				System.exit(0);
			}
			return comparisonValue;
		}/*  end of compare method  */
	}/*  end of byMeanSignal class  */
	
	
}/*  end of ChipAnalyzer class */